﻿using OpenCvSharp;
using System;
namespace FLANN
{
    public class ContourMatcher
    {
        public Mat Results;
        private int _InhibitionRatio;
        private int _DisplayRatio;
        private int MinArea;
        private double ShapeDifferenceScore;
        private double AreaRatioDifference;

        internal class MyResult
        {
            internal int X;
            internal int Y;
            internal double Angle;

            internal MyResult(int x,int y,double angle)
            {
                X = x;
                Y = y;
                Angle = angle;
            }
        }

        internal class Template
        {
            public double FillRatio;
            public Point[] Contour; 
            public Template(double fill_ratio, Point[] countour)
            {
                FillRatio = fill_ratio;
                Contour = countour;
            }
        }

        public ContourMatcher(double shape_difference_score,double area_diff,int min_area,int inhibition_ratio,int display_ratio)
        {
            ShapeDifferenceScore = shape_difference_score;
            AreaRatioDifference = area_diff;
            MinArea = min_area;
            _InhibitionRatio = inhibition_ratio;
            _DisplayRatio = display_ratio;
        }
        private Template MakeTemplate(Mat CurrentPicture)
        {
            int origin_x, origin_y, origin_witdh, origin_height;
            
            var small_image = CurrentPicture.Clone();
            small_image = small_image.Resize(new OpenCvSharp.Size(1224, 1024));
            var rect = Cv2.SelectROI(small_image);
            Cv2.DestroyWindow("ROI selector");
            origin_x = (int) (rect.Left / 1224.0f * 2448);
            origin_y = (int) (rect.Top / 1024.0f * 2048);
            origin_witdh = (int) (rect.Width / 1224.0f * 2448);
            origin_height = (int) (rect.Height / 1024.0f * 2048);
            var origin_rect = new Rect(origin_x, origin_y, origin_witdh, origin_height);
            var template = new Mat(CurrentPicture, origin_rect); 
            
            Point[][] contours = new Point[][]{};
            // 二值化并绘制轮廓
            int key='0';
            Cv2.NamedWindow("target",WindowFlags.AutoSize);
            int lower_position = 100;
            int upper_position = 200;
            Cv2.CreateTrackbar("lower", "target", ref lower_position,255);
            Cv2.CreateTrackbar("upper", "target", ref upper_position, 255);
            while (key!='s')
            {
                Mat background = template.Clone();

                // Cv2.PutText(background,Cv2.GetTrackbarPos("阈值","target").ToString(),
                //     new Point(50,50),HersheyFonts.Italic,1,Scalar.Red);
                // Cv2.PutText(background,background.Width.ToString(),new Point(0,70),
                //     HersheyFonts.HersheySimplex,1,Scalar.Red);
                lower_position = Cv2.GetTrackbarPos("lower", "target");
                upper_position = Cv2.GetTrackbarPos("upper", "target");
                TemplateInformation(background,out contours,lower_position,upper_position);
                
                Cv2.DrawContours(background, contours, -1,Scalar.Red);
                Cv2.ImShow("target",background);
                key = Cv2.WaitKey(10);
                
            }
            //Cv2.DestroyWindow("target");
            
            // 选择最大面积
            int max_square = -1;
            int index = -1;
            for (int i = 0; i < contours.Length; i++)
            {
                var square = (int)Cv2.ContourArea(contours[i]);
                if (Cv2.ContourArea(contours[i]) > max_square)
                {
                    max_square = square;
                    index = i;
                }
            }
            
            
            // 计算轮廓的填充率
            var template_rotate_rectangle = Cv2.MinAreaRect(contours[index]);
            var fill_ratio = Cv2.ContourArea(contours[index]) /
                             (template_rotate_rectangle.Size.Height * template_rotate_rectangle.Size.Width);
            var show = template.Clone();
            Cv2.DrawContours(show,contours,index,Scalar.Red,1);
            show.Resize(new OpenCvSharp.Size(show.Rows * 3, show.Cols * 3));
            Cv2.PutText(show,Cv2.ContourArea(contours[index]).ToString(),new Point(0,30),
                HersheyFonts.HersheySimplex,1,Scalar.Red);
            Cv2.ImShow("result",show);
            Cv2.WaitKey(0);
            return new Template(fill_ratio,contours[index]);
        }
        public Tuple<int, int, double> Matching(Mat image, Mat template_src)
        {

            Point[][] image_contours;
            Results = image.Clone();
            var template = MakeTemplate(template_src);
            ContoursInformation(image, out image_contours, _InhibitionRatio, _DisplayRatio);
            
            

            // 遍历所有轮廓和模板轮廓进行匹配

            int min_pos = 0;
            for (int i = 0; i < image_contours.Length; i++)
            {
                
                var square = Cv2.ContourArea(image_contours[i]);
                if (square > MinArea)
                {
                    Cv2.DrawContours(Results,image_contours,i,Scalar.Red,2);
                    double diff_score = Cv2.MatchShapes(image_contours[i], template.Contour, ShapeMatchModes.I3);
                    if (diff_score < ShapeDifferenceScore)
                    {
                        min_pos = i;

                        var rectangle = Cv2.MinAreaRect(image_contours[i]);
                        double fill_ratio = Cv2.ContourArea(image_contours[i]) /
                                            (rectangle.Size.Height * rectangle.Size.Width);
                        if (Math.Abs(template.FillRatio - fill_ratio) > AreaRatioDifference) continue;
                        
                        // 输出归一化的角度 范围在0~180
                        var norm_rectangle = GeometryFeatureModule.StandardizeRotatedRectangle(rectangle);

                        var target = new MyResult((int) norm_rectangle.Center.X, (int) norm_rectangle.Center.Y,
                            norm_rectangle.Angle);

                        Cv2.DrawContours(Results, image_contours, min_pos, Scalar.Red, 3);
                        Cv2.Circle(Results, new Point(target.X, target.Y), 3, Scalar.Red, -1);

                    }
                }
                
            }

            var origin = Results.Clone();
            // Cv2.ImShow("Origin",origin);
            Results = Results.Resize(new Size(1224,1024 ));
            Cv2.ImShow("result",Results);
            Cv2.WaitKey(0);
            return new Tuple<int, int, double>(-2, -2, -2);
        }

        private void ContoursInformation(Mat image, out Point[][] contours, int lower=0,int upper=0)
        {
            var gray = new Mat();
            var binary = new Mat();

            Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
            Mat tmp = new Mat();
            
            // Cv2.EqualizeHist(gray,gray);
            // using (Mat equalize = gray.Clone())
            // {
            //     Cv2.ImShow("equalizeHist",equalize);
            // }

            // Cv2.Threshold(gray, gray, 125, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
            // Cv2.ImShow("Binary",tmp);
            
            Cv2.Blur(gray,binary,new Size(5,5));
            // Cv2.MedianBlur(gray,binary,13);
            // Cv2.GaussianBlur(gray,binary,new Size(5,5));
            // Cv2.ImShow("Gauss",gray);
            // Cv2.ImShow("Median",binary);
            // Cv2.ImShow("Blur",binary);
            gray = binary;
            //Canny边缘检测
            Mat canny_Image = new Mat();
            Cv2.Canny(gray, canny_Image, lower, upper);
            tmp = canny_Image;
            Cv2.ImShow("canny_Image",tmp);
            
            Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
            // Cv2.MorphologyEx(binary, binary, MorphTypes.Open, element, new Point(-1, -1));
            // Cv2.MorphologyEx(binary, binary, MorphTypes.Close, element, new Point(-1, -1));
            Cv2.Dilate(canny_Image,canny_Image,element);
            Cv2.FindContours(canny_Image, out contours, out _, RetrievalModes.External,
                ContourApproximationModes.ApproxSimple);
            canny_Image = canny_Image.Resize(new Size(1224, 1024));
            Cv2.ImShow("gray",canny_Image);
        }
            
        public void TemplateInformation(Mat image, out Point[][] template_contours, int lower,int upper)
        {
            ContoursInformation(image, out template_contours, lower,upper);
        }

        private bool IsSameTarget(MyResult A, MyResult B)
        {
            int dx = Math.Abs(A.X - B.X) ;
            int dy = Math.Abs(A.Y - B.Y) ;
            if (dx * dx + dy * dy < 100) return true;
            return false;
        }
    }
}